/**
 * <p>Original Author: Daniel Freeman    http://www.danielfreeman.asia</p>
 *
 * <p>Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:</p>
 *
 * <p>The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.</p>
 *
 * <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS' OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.</p>
 *
 * <p>Licensed under The MIT License</p>
 * <p>Redistributions of files must retain the above copyright notice.</p>
 * <p>You are permitted to modify and enhance this code,</p>
 * <p>As a courtesy, please credit the original author, Daniel Freeman   http://www.danielfreeman.asia</p>
 */

// Procedural Generation Of Room Mesh

package {
	[SWF(frameRate="60")]

	import com.adobe.utils.*;

	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.textures.Texture;
	import flash.events.Event;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	import flash.utils.ByteArray;


	public class RoomExperiment0 extends Sprite {

		protected static const COLOURS:Vector.<uint>=Vector.<uint>([0x333333, 0xCCCCC, 0x111111, 0x662200]); // Colour palette
		protected static const CORRIDOR_SEGMENT_WIDTH:Number=0.5;
		protected static const PARTITION_WIDTH:Number=0.1;

		protected static const CORRIDOR:Vector.<Number>=Vector.<Number>([-0.65, 0, 0, -0.5, 0, 3, -0.6, 0.1, 2, -0.8, 0.90, 1, -0.20, 0.85, 0, 0.8, 0.85, 0, 0.8, 4.0, 0]);
		protected static const PARTITION:Vector.<Number>=Vector.<Number>([-0.47, 0, 2, -0.7, 0.75, 1, -0.5, 0.80, 1, 0.9, 0.80, 0, 0.9, 4.0, 0]);
		protected static const LINKS:Vector.<int>=Vector.<int>([0, 3, 4, 5]);

		protected static const CORRIDOR_ANGLE:Number=10;
		protected static const CORRIDOR_LENGTH:int=18;

		protected static const N:int=8;

		protected var _indices:Vector.<uint>=new Vector.<uint>;
		protected var _reverseIndices:Vector.<uint>=new Vector.<uint>;

		protected var _vertices:Vector.<Number>=new Vector.<Number>;

		protected var _context3D:Context3D;
		protected var _shaderProgram:Program3D;
		protected var _vertexBuffer:VertexBuffer3D;
		protected var _indexBuffer:IndexBuffer3D;
		protected var _reverseIndexBuffer:IndexBuffer3D;
		protected var _modelMatrix:Matrix3D=new Matrix3D();
		protected var _rotationMatrix:Matrix3D;
		protected var _finalMatrix:Matrix3D=new Matrix3D();

		protected var _projectionMatrix:PerspectiveMatrix3D=new PerspectiveMatrix3D();

		protected var _t:Number=0;

		protected var _fragmentShaderAssembler:ByteArray;
		protected var _vertexShaderAssembler:ByteArray;
		protected var _reflectionShaderAssembler:ByteArray;

		public function RoomExperiment0(screen:Sprite=null) {
			if (screen) {
				screen.addChild(this);
			}

			stage.scaleMode=StageScaleMode.NO_SCALE;
			stage.align=StageAlign.TOP_LEFT;

			generateCorridor(CORRIDOR_LENGTH, CORRIDOR, PARTITION, CORRIDOR_SEGMENT_WIDTH, PARTITION_WIDTH, LINKS, CORRIDOR_ANGLE, true);

			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
		}


		protected function generateCorridor(n:int, segment:Vector.<Number>, divider:Vector.<Number>, segmentWidth:Number, dividerWidth:Number, links:Vector.<int>, rotate:Number=0, join:Boolean=false):void {
			var transform:Matrix3D=new Matrix3D();
			var transform2:Matrix3D=new Matrix3D();
			var x:Number;
			var y:Number;
			var colour:Vector3D;
			var vector0:Vector3D;
			var vector1:Vector3D;
			var i:int;

			for (var s:int=0; s < n; s++) {

				var start0:int=_vertices.length / N;

				for (i=0; i < divider.length; i+=3) {
					x=divider[i];
					y=divider[i + 1];
					colour=toColourVector(COLOURS[divider[i + 2]]);
					vector0=transform.transformVector(new Vector3D(x, y, 0));
					vector1=transform.transformVector(new Vector3D(x, y, dividerWidth));
					_vertices.push(vector0.x, vector0.y, vector0.z, 0.0, (i % 2 == 0) ? 0.0 : 1.0, colour.x, colour.y, colour.z);
					_vertices.push(vector1.x, vector1.y, vector1.z, 1.0, (i % 2 == 0) ? 0.0 : 1.0, colour.x, colour.y, colour.z);
				}

				if (s > 0) {
					for (i=0; i < links.length - 1; i++) {
						_indices.push(start1 + links[i] * 2 + 1, start0 + i * 2, start0 + i * 2 + 2, start1 + links[i] * 2 + 1, start0 + i * 2 + 2, start1 + links[i + 1] * 2 + 1);
						_reverseIndices.push(start0 + i * 2 + 2, start0 + i * 2, start1 + links[i] * 2 + 1, start1 + links[i + 1] * 2 + 1, start0 + i * 2 + 2, start1 + links[i] * 2 + 1);
					}
				}

				for (i=start0; i < start0 + divider.length * 2 / 3 - 2; i+=2) {
					_indices.push(i, i + 1, i + 3, i, i + 3, i + 2);
					_reverseIndices.push(i + 3, i + 1, i, i + 2, i + 3, i);
				}

				transform.copyToMatrix3D(transform2);
				transform2.appendTranslation(0.0, 0.0, dividerWidth);
				transform2.appendRotation(rotate, Vector3D.Y_AXIS);
				transform2.appendTranslation(0.0, 0.0, segmentWidth);
				transform2.appendRotation(rotate, Vector3D.Y_AXIS);

				var start1:int=_vertices.length / N;

				for (i=0; i < segment.length; i+=3) {
					x=segment[i];
					y=segment[i + 1];
					colour=toColourVector(COLOURS[segment[i + 2]]);
					vector0=transform.transformVector(new Vector3D(x, y, dividerWidth));
					vector1=transform2.transformVector(new Vector3D(x, y, 0.0));
					_vertices.push(vector0.x, vector0.y, vector0.z, 0.0, (i % 2 == 0) ? 0.0 : 1.0, colour.x, colour.y, colour.z);
					_vertices.push(vector1.x, vector1.y, vector1.z, 1.0, (i % 2 == 0) ? 0.0 : 1.0, colour.x, colour.y, colour.z);
				}

				for (i=0; i < links.length - 1; i++) {
					_indices.push(start0 + (i * 2) + 1, start1 + links[i] * 2, start1 + links[i + 1] * 2, start0 + (i * 2) + 1, start1 + links[i + 1] * 2, start0 + (i * 2) + 3);
					_reverseIndices.push(start1 + links[i + 1] * 2, start1 + links[i] * 2, start0 + (i * 2) + 1, start0 + (i * 2) + 3, start1 + links[i + 1] * 2, start0 + (i * 2) + 1);
				}

				for (i=start1; i < start1 + segment.length * 2 / 3 - 2; i+=2) {
					_indices.push(i, i + 1, i + 3, i, i + 3, i + 2);
					_reverseIndices.push(i + 3, i + 1, i, i + 2, i + 3, i);
				}

				transform2.copyToMatrix3D(transform);
			}

			if (join) {
				for (i=0; i < links.length - 1; i++) {
					_indices.push(start1 + links[i] * 2 + 1, i * 2, i * 2 + 2, start1 + links[i] * 2 + 1, i * 2 + 2, start1 + links[i + 1] * 2 + 1);
					_reverseIndices.push(i * 2 + 2, i * 2, start1 + links[i] * 2 + 1, start1 + links[i + 1] * 2 + 1, i * 2 + 2, start1 + links[i] * 2 + 1);
				}
			}
		}


		protected function onContext3DCreate(event:Event):void {
			_context3D=Stage3D(event.currentTarget).context3D;

			if (!_context3D) {
				return;
			}

			_context3D.enableErrorChecking=true;
			initialiseData();
		}


		protected function distanceToCentre(rotate:Number, segmentWidth:Number, dividerWidth:Number):Number {
			var d:Number=segmentWidth + dividerWidth * Math.cos(rotate / 180 * Math.PI);
			return (d / 2) / Math.tan(rotate / 180 * Math.PI);
		}


		protected function toColourVector(value:uint):Vector3D {
			return new Vector3D((value >> 16 & 0xff) / 256, (value >> 8 & 0xff) / 256, (value & 0xff) / 256, 1.0);
		}


		protected function initialiseData():void {

			// Back Buffer
			_context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2, true);
			_context3D.setCulling(Context3DTriangleFace.FRONT);

			// Upload Indices
			_indexBuffer=_context3D.createIndexBuffer(_indices.length);
			_indexBuffer.uploadFromVector(_indices, 0, _indices.length);

			// Upload Indices Of Reflection
			_reverseIndexBuffer=_context3D.createIndexBuffer(_reverseIndices.length);
			_reverseIndexBuffer.uploadFromVector(_reverseIndices, 0, _reverseIndices.length);

			// Upload Vertex data
			_vertexBuffer=_context3D.createVertexBuffer(_vertices.length / N, N);
			_vertexBuffer.uploadFromVector(_vertices, 0, _vertices.length / N);

			var assembler:AGALMiniAssembler=new AGALMiniAssembler();

			assembler.assemble(Context3DProgramType.VERTEX, "m44 vt0, va0, vc0 \n" + // vertex transformation
				"mov op, vt0 \n" + // output transformed vertex
				"mov v1, va1 \n" + // v1 = uv
				"mov v0, va2 \n" + // v0 = rgb

				// Z-fog
				"div vt0.z vt0.z vc4.z \n" + // divide distance from camera by vc4.z
				"sub v2, vc4.w, vt0.z \n" // v2 = 1 - scaled distance
				);
			_vertexShaderAssembler=assembler.agalcode;

			assembler.assemble(Context3DProgramType.VERTEX, "m44 vt0, va0, vc0 \n" + // vertex transformation
				"mov op, vt0 \n" + // output transformed vertex
				"mov v1, va1 \n" + // v1 = uv
				"mov v0, va2 \n" + // v0 = rgb

				// Z-fog
				"div vt0.z, vt0.z, vc4.z \n" + // divide distance from camera by vc4.z
				"sub vt0.z, vc4.w, vt0.z \n" + // 1 - scaled distance

				// Y-fog
				"div vt0.y, vt0.y, vc4.y \n" + // divide distance from camera by vc4.z
				"add vt0.y, vc4.x, vt0.y \n" + // vc4.x + scaled distance (because y distance is -ve)

				"mul v2, vt0.y, vt0.z" // multiply Z and Y fog
				);
			_reflectionShaderAssembler=assembler.agalcode;

			// A simple fragment shader which will use the vertex position as a colour

			assembler.assemble(Context3DProgramType.FRAGMENT, "sub ft0.x, fc0.w, v1.x \n" + "sub ft0.y, fc0.w, v1.y \n" +

				"min ft0.z, ft0.x, ft0.y \n" + "min ft0.z, ft0.z, v1.x \n" + "min ft0.z, ft0.z, v1.y \n" + // minimum distance from corner of uv rect

				"mul ft1.xyz, fc0.xyz, ft0.zzz \n" + // multiply by detail colour
				"add ft1.xyz, v0.xyz, ft1.xyz \n" + // add ambient colour
				"sat ft0.xyz, v2 \n" + "mul ft0, ft1.xyz, ft0.xyz \n" + // multiply by fog
				"add oc, ft0, fc1" // output colour
				);
			_fragmentShaderAssembler=assembler.agalcode;

			_shaderProgram=_context3D.createProgram();

			_projectionMatrix.perspectiveFieldOfViewLH(45.0 * Math.PI / 180, stage.stageWidth / stage.stageHeight, 0.1, 1000.0);

			_modelMatrix.identity();
			_modelMatrix.appendTranslation(-0.3, -0.4, 0.6);
			_modelMatrix.appendRotation(10, Vector3D.Y_AXIS);
			_rotationMatrix=_modelMatrix.clone();

			// Start animating
			addEventListener(Event.ENTER_FRAME, enterFrame);
		}


		protected function enterFrame(event:Event):void {
			const rotate:Number=-28 * 0.04;
			_context3D.clear();
			_context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); //va0  xyz
			_context3D.setVertexBufferAt(1, _vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2); //va1  uv
			_context3D.setVertexBufferAt(2, _vertexBuffer, 5, Context3DVertexBufferFormat.FLOAT_3); //va1  rgb

			_context3D.setProgram(_shaderProgram);

			_modelMatrix.copyToMatrix3D(_finalMatrix);
			_finalMatrix.append(_projectionMatrix);
			_t+=1;

			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _finalMatrix, true); // va0 - va3  Transformation Matrix
			_context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, Vector.<Number>([0.8, 4.0, 6.0, 1.0])); // va4  0.0, y-fog, z-fog, 1.0
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([1.0, 1.0, 1.0, 1.0])); // fc0  detail colour
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0.0, 0.0, 0.0, 1.0])); // fc1  ambient colour
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, Vector.<Number>([0.4, 0.4, 0.4, 1.0])); // fc1  ambient colour

			_shaderProgram.upload(_vertexShaderAssembler, _fragmentShaderAssembler);
			_context3D.drawTriangles(_indexBuffer);

			_modelMatrix.appendRotation(rotate, Vector3D.Y_AXIS);

			_modelMatrix.copyToMatrix3D(_finalMatrix);
			_finalMatrix.appendScale(1.0, -1.0, 1.0);
			_finalMatrix.appendTranslation(0.0, -0.8, 0.0); // invert and move mesh to form reflection
			_finalMatrix.append(_projectionMatrix);
			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _finalMatrix, true);

			_shaderProgram.upload(_reflectionShaderAssembler, _fragmentShaderAssembler);
			_context3D.drawTriangles(_reverseIndexBuffer);

			_context3D.present();
		}

	}
}
